#include "simple_socket_protocol/Server.h"

#include <iostream>
#include <unistd.h>
#include <thread>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

using namespace ssp;

const u_short BROADCAST_PORT{0x3113U ^ 0x5181U ^ 0x2181U ^ 0x5143U + 0x1192U ^ 0x0U}; //"camera broadcast"
const u_short TCP_PORT{0x3113U ^ 0x5181U ^ 0x2031U ^ 0x6U};                           //"camera tcp"

ProtocolData RecvFrom(int s)
{
    if (s == -1)
    {
        return ProtocolData();
    }
    uint32_t dataLen{0};
    char buf[1024]{0};
    int len = recv(s, buf, sizeof(buf), MSG_NOSIGNAL);
    dataLen = *(uint32_t *)buf + 4 - len;
    char *p = buf + 4;
    while (dataLen > 0)
    {
        len = recv(s, p, dataLen, 0);
        if (len <= 0)
        {
            break;
        }
        p += len;
        dataLen -= len;
    }
    return ProtocolData::fromRawData((const uint8_t *)buf);
}

ProtocolData RecvFromUDP(int s, sockaddr_in &addr, uint32_t bufSize = 1024)
{
    if (s == -1)
    {
        return ProtocolData();
    }
    socklen_t addrLen{sizeof(sockaddr_in)};
    uint32_t dataLen{0};
    char *buf{new char[bufSize]};
    int len = recvfrom(s, buf, bufSize, MSG_NOSIGNAL, (sockaddr *)&addr, &addrLen);
    dataLen = *(uint32_t *)buf + 4 - len;
    char *p = buf + 4;
    while (dataLen > 0)
    {
        len = recvfrom(s, p, dataLen, 0, (sockaddr *)&addr, &addrLen);
        if (len <= 0)
        {
            break;
        }
        p += len;
        dataLen -= len;
    }
    ProtocolData ret{ProtocolData::fromRawData((const uint8_t *)buf)};
    delete buf;
    return ret;
}

bool SendTo(int s, const ProtocolData &data)
{
    if (s == -1)
    {
        return false;
    }
    rawDataSize_t rawDataLen{data.getRawDataSize()};
    uint8_t *rawData{new uint8_t[rawDataLen]};
    data.toRawData(rawData, rawDataLen);
    ssize_t sendLen{send(s, (const char *)rawData, rawDataLen, MSG_NOSIGNAL)};
    delete rawData;
    return sendLen == rawDataLen;
}

bool SendTo(int s, sockaddr_in &addr, const ProtocolData &data)
{
    if (s == -1)
    {
        return false;
    }
    rawDataSize_t rawDataLen{data.getRawDataSize()};
    uint8_t *rawData{new uint8_t[rawDataLen]};
    data.toRawData(rawData, rawDataLen);
    ssize_t sendLen{sendto(s, (const char *)rawData, rawDataLen, MSG_NOSIGNAL, (sockaddr *)&addr, sizeof(sockaddr_in))};
    delete rawData;
    return sendLen == rawDataLen;
}

bool IsTCPBroken(int s)
{
    struct tcp_info info;
    int len = sizeof(info);
    getsockopt(s, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
    if ((info.tcpi_state == TCP_ESTABLISHED))
    {
        return false;
    }
    return true;
}

class Server::ServerImpl
{
public:
    static void recvAndResponser(ServerImpl *server)
    {
        while (server->m_udpSocket != -1)
        {
            sockaddr_in addr{0};
            ProtocolData data{RecvFromUDP(server->m_udpSocket, addr)};
            if (data.getInfo() == "ssp_find_server")
            {
                std::cout << "Client looking for server from " << inet_ntoa(addr.sin_addr) << std::endl;
                data.setInfo("ssp_im_server");
                SendTo(server->m_udpSocket, addr, data);
            }
        }
        perror("recvAndResponser stopped");
    }

    static void connectionFresher(ServerImpl *server)
    {
        sockaddr_in addr{0};
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        addr.sin_port = htons(TCP_PORT);
        while (server->m_tcpSocket == -1)
        {
            server->m_tcpSocket = socket(AF_INET, SOCK_STREAM, 0);
            if (bind(server->m_tcpSocket, (struct sockaddr *)&addr, sizeof(addr)))
            {
                perror("tcp bind failed\t");
                close(server->m_tcpSocket);
                server->m_tcpSocket = -1;
                sleep(1);
                continue;
            }
            if (listen(server->m_tcpSocket, 1))
            {
                perror("tcp listen failed");
                close(server->m_tcpSocket);
                server->m_tcpSocket = -1;
                continue;
            }
        }
        while (server->m_tcpSocket != -1)
        {
            if (server->m_clientSocket == -1)
            {
                server->m_clientSocket = accept(server->m_tcpSocket, nullptr, nullptr);
            }
            else if (IsTCPBroken(server->m_clientSocket))
            {
                perror("tcp broken");
                close(server->m_clientSocket);
                server->m_clientSocket = -1;
            }
        }
        perror("connectionFresher stopped");
    }

    ServerImpl()
        : m_udpSocket(-1),
          m_tcpSocket(-1),
          m_clientSocket(-1),
          m_udpThread(),
          m_tcpThread()
    {
        m_udpSocket = socket(AF_INET, SOCK_DGRAM, 0);
        if (m_udpSocket == -1)
        {
            perror("udp socket failed");
            return;
        }

        int broadcastEnable = 1;
        if (-1 == setsockopt(m_udpSocket, SOL_SOCKET, SO_BROADCAST, (const char *)&broadcastEnable, sizeof(broadcastEnable)))
        {
            perror("udp setsockopt failed");
            close(m_udpSocket);
            m_udpSocket = -1;
            return;
        }

        sockaddr_in addr{0};
        addr.sin_family = AF_INET;
        addr.sin_port = htons(BROADCAST_PORT);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        if (bind(m_udpSocket, (struct sockaddr *)&addr, sizeof(addr)) == -1)
        {
            perror("udp bind failed");
            close(m_udpSocket);
            m_udpSocket = -1;
            return;
        }

        m_udpThread = std::thread(recvAndResponser, this);
        m_tcpThread = std::thread(connectionFresher, this);
    }

    ~ServerImpl()
    {
        if (m_udpSocket != -1)
        {
            close(m_udpSocket);
        }
        if (m_tcpSocket != -1)
        {
            close(m_tcpSocket);
        }
        if (m_clientSocket != -1)
        {
            close(m_clientSocket);
        }
        m_udpThread.detach();
        m_tcpThread.detach();
    }

    ProtocolData recv()
    {
        ProtocolData data{RecvFrom(m_clientSocket)};
        return data;
    }

    bool send(const ProtocolData &data)
    {
        bool ret{SendTo(m_clientSocket, data)};
        return ret;
    }

    int m_udpSocket;
    int m_tcpSocket;
    int m_clientSocket;
    std::thread m_udpThread;
    std::thread m_tcpThread;
};

Server::Server()
    : m_impl(new ServerImpl())
{
}

Server::~Server()
{
    delete m_impl;
}

ProtocolData Server::recv() const
{
    return m_impl->recv();
}

bool Server::send(const ProtocolData &data) const
{
    return m_impl->send(data);
}

bool Server::isConnectionEstablished() const
{
    return m_impl->m_clientSocket != -1;
}
